package com.mindata.blockchain.block.check.local;

import cn.hutool.core.util.StrUtil;
import com.mindata.blockchain.block.Block;
import com.mindata.blockchain.block.Instruction;
import com.mindata.blockchain.block.check.BlockChecker;
import com.mindata.blockchain.common.BlockUtils;
import com.mindata.blockchain.common.ResultPack;
import com.mindata.blockchain.common.Sha256;
import com.mindata.blockchain.common.exception.TrustSDKException;
import com.mindata.blockchain.core.manager.DbBlockManager;
import com.mindata.blockchain.core.manager.PermissionManager;
import com.mindata.blockchain.core.service.BlockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * 使用本地存储的权限、Block信息对新来的block进行校验
 *
 * @author wuweifeng wrote on 2018/3/13.
 */
@Component
public class DbBlockChecker implements BlockChecker {
    private static final Logger logger = LoggerFactory.getLogger(DbBlockChecker.class);
    @Resource
    private DbBlockManager dbBlockManager;
    @Resource
    private PermissionManager permissionManager;

    @Resource
    private BlockService blockService;

    @Override
    public int checkNum(Block block) {
        Block localBlock = getLastBlock();
        long localNum = 0;
        if (localBlock != null) {
            localNum = localBlock.getBlockHeader().getNumber();
        }
        //本地区块+1等于新来的区块时才同意
        if (localNum + 1 == block.getBlockHeader().getNumber()) {
            //同意生成区块
            return 0;
        }

        //拒绝
        return -1;
    }

    @Override
    public int checkPermission(Block block) {
        //校验对表的操作权限
        return permissionManager.checkPermission(block) ? 0 : -1;
    }

    @Override
    public int checkHash(Block block) {
        Block localLast = getLastBlock();
        //创世块可以，或者新块的prev等于本地的last hash也可以
        if (localLast == null && block.getBlockHeader().getHashPreviousBlock() == null) {
            return 0;
        }
        if (localLast != null && StrUtil.equals(localLast.getHash(), block.getBlockHeader().getHashPreviousBlock())) {
            return 0;
        }
        return -1;
    }

    @Override
    public int checkTime(Block block) {
        Block localBlock = getLastBlock();
        //新区块的生成时间比本地的还小
        if (localBlock != null && block.getBlockHeader().getTimeStamp() <= localBlock.getBlockHeader().getTimeStamp()) {
            //拒绝
            return -1;
        }
        return 0;
    }

    @Override
    public int checkSign(Block block) {
        if (!checkBlockHashSign(block)) {
            return -1;
        }
        return 0;
    }

    private Block getLastBlock() {
        return dbBlockManager.getLastBlock();
    }

    @Override
    public String checkBlock(Block block) {
        if (!checkBlockHashSign(block)) return block.getHash();

        String preHash = block.getBlockHeader().getHashPreviousBlock();
        if (preHash == null) return null;

        Block preBlock = dbBlockManager.getBlockByHash(preHash);
        if (preBlock == null) return block.getHash();

        long localNum = preBlock.getBlockHeader().getNumber();
        //当前区块+1等于下一个区块时才同意
        if (localNum + 1 != block.getBlockHeader().getNumber()) {
            return block.getHash();
        }
        if (block.getBlockHeader().getTimeStamp() <= preBlock.getBlockHeader().getTimeStamp()) {
            return block.getHash();
        }


        return null;
    }

    /**
     * 校验区块中指令
     *
     * @param block
     * @return
     */
    @Override
    public int checkBlockInstruction(Block block) {
        List<Instruction> instructions = block.getBlockBody().getInstructions();
        for (Instruction ins : instructions) {
            //如果本地区块中已经存在指令，则校验失败
            if (dbBlockManager.getInstructionInfoByHash(ins.getHash()) != null) {
                return -1;
            }
        }
        return 0;
    }

    /**
     * 检测区块签名及hash是否符合
     *
     * @param block
     * @return
     */
    private boolean checkBlockHashSign(Block block) {
        try {
            ResultPack resultPack = blockService.check(block.getBlockBody());
            if (resultPack.isFailed()) {
                logger.warn("区块校验失败:{}", resultPack.comment());
                return false;
            }
        } catch (TrustSDKException e) {
            logger.error("check error", e);
            return false;
        }

        String hash = Sha256.sha256(BlockUtils.getBlockSignStr(block));
        if (!StrUtil.equals(block.getHash(), hash)) {
            return false;
        }
        return true;
    }

}
